<template>
  <div class="container">
    <!-- ============================================= 画布视图 ============================================= -->
    <div
      :id="canvasId"
      :class="canvasClass"
      :style="canvasStyle"
      :ref="canvasRef"
      @contextmenu="globalClickedDispatch"
    ></div>

    <!-- ============================================= 左侧工具栏 ============================================= -->
    <div class="toolbar" v-if="toolBar.list.length > 0" :class="toolBar.position">
      <ul>
        <li @click="setZoomOut" title="放大" v-if="toolBar.list.includes('zoomOut')">
          <i class="iconfont icon-zoomin"></i>
        </li>
        <li @click="setZoomIn" title="缩小" v-if="toolBar.list.includes('zoomIn')">
          <i class="iconfont icon-zoomout"></i>
        </li>
        <li @click="saveImage" title="保存图片" v-if="toolBar.list.includes('saveImage')">
          <i class="iconfont icon-baocun-"></i>
        </li>
        <li @click="exportJson" title="导出JSON" v-if="toolBar.list.includes('saveJson')">
          <i class="iconfont icon-json"></i>
        </li>
        <li @click="showOverView" title="缩略图" v-if="toolBar.list.includes('overView')">
          <i class="iconfont icon-suolvetu" style="font-size: 14px"></i>
        </li>
        <li @click="clockwiseRotate" title="顺时针旋转" v-if="toolBar.list.includes('clockwise')">
          <i class="iconfont icon-shunshizhenfangxiangclockwise4" style="font-size: 14px"></i>
        </li>
        <li
          @click="counterclockwiseRotate"
          title="逆时针旋转"
          v-if="toolBar.list.includes('counterclockwise')"
        >
          <i class="iconfont icon-nishizhencounterclockwise3" style="font-size: 14px"></i>
        </li>
        <li
          @click="setMouseModel('normal')"
          title="正常模式"
          v-if="toolBar.list.includes('normalModel')"
        >
          <i class="iconfont icon-pointer-up"></i>
        </li>
        <li
          @click="setMouseModel('drag')"
          title="拖拽模式"
          v-if="toolBar.list.includes('dragModel')"
        >
          <i class="iconfont icon-line-dragmovetuozhuai-01"></i>
        </li>
        <li
          @click="setMouseModel('select')"
          title="框选模式"
          v-if="toolBar.list.includes('selectModel')"
        >
          <i class="iconfont icon-kuangxuan1"></i>
        </li>
        <li @click="fullScreen" title="全屏显示" v-if="toolBar.list.includes('screenFull')">
          <i class="iconfont icon-quanping" style="font-size: 20px"></i>
        </li>
      </ul>
    </div>

    <!-- ============================================= 右键菜单 ============================================= -->
    <div id="nodeMenuDialog" class="nodeMenuDialog">
      <ul>
        <li @click="clickNodeInfo">节点信息</li>
        <li @click="settingNode">配置节点</li>
        <li @click="selectRelation">选中关联</li>
        <li @click="deleteNode">删除节点</li>
        <li @click="contractNode">收起节点</li>
        <li @click="expandedNode">展开节点</li>
      </ul>
    </div>
  </div>
</template>

<script>
import VisGraph from '@/assets/GraphVis/old/visgraph.min.js';
import LayoutFactory from '@/assets/GraphVis/old/visgraph-layout.min.js';
console.log('VisGraph', VisGraph);
console.log('LayoutFactory', LayoutFactory);

// 全屏组件
import screenfull from 'screenfull';
export default {
  name: 'graph',
  components: {
    // VisGraph,
    // LayoutFactory,
  },
  props: {
    // 画布id
    canvasId: {
      type: String,
      default: 'graph-container',
    },
    // 画布class
    canvasClass: {
      type: String,
      default: 'graph-container',
    },
    // 画布ref
    canvasRef: {
      type: String,
      default: 'graphContainer',
    },
    // 画布样式
    canvasStyle: {
      type: Object,
      default: () => {
        return {
          width: '100%',
          height: '100%',
        };
      },
    },
    // 节点的默认配置
    canvasConfig: {
      type: Object,
      default: () => {
        return {
          // 节点配置
          node: {
            label: {
              // 标签配置
              show: true, // 是否显示
              color: '250,250,250', // 字体颜色
              font: 'normal 14px Microsoft YaHei', // 字体大小及类型
              textPosition: 'Middle_Center', // 字体位置
              wrapText: true, // 节点包裹文字(该属性为true时只对于字体位置为Middle_Center时有效)
            },
            shape: 'circle', // 节点形状 circle,rect,square,ellipse,triangle,star,polygon,text
            // width: 60, // 节点宽度(只对于shape为rect时有效)
            // height: 60, // 节点高度(只对于shape为rect时有效)
            color: '62,160,250', // 节点颜色
            borderColor: '62,160,250', // 节点边框颜色
            borderWidth: 0, // 节点边框宽度
            borderRadius: 0, // 节点圆角
            lineDash: [0], // 节点边框线条类型 [0] 表示实线 [5,8] 表示虚线 borderWidth > 0有效
            alpha: 1, // 节点透明度
            size: 60, // 节点大小
            selected: {
              // 节点选中后样式
              borderColor: '136,198,255', // 选中时边框颜色
              borderAlpha: 1, // 选中时的边框透明
              borderWidth: 3, // 选中是的边框宽度
              showShadow: true, // 是否展示阴影
              shadowColor: '136,198,255', // 选中是的阴影颜色
            },
          },
          // 线条配置
          link: {
            label: {
              // 标签配置
              show: true, // 是否显示
              color: '100,100,200', // 标签颜色
              font: 'normal 10px Arial', // 标签文字大小及类型
            },
            lineType: 'direct', // 线条类型direct,curver,vlink,hlink,bezier,vbezier,hbezier
            colorType: 'defined', // 连线颜色类型 source:继承source颜色,target:继承target颜色 both:用双边颜色，defined:自定义
            color: '200,200,200', // 线条颜色
            alpha: 1, // 连线透明度
            lineWidth: 1, // 连线宽度
            lineDash: [0], // 虚线间隔样式如：[5,8]
            showArrow: true, // 显示箭头
            selected: {
              // 选中时的样式设置
              color: '20,250,50', // 选中时的颜色
              alpha: 1, // 选中时的透明度
              lineWidth: 4, // 选中线条宽度
              showShadow: true, // 显示阴影
              shadowColor: '50,250,50', // 阴影颜色
            },
          },
          highLightNeiber: true, // 相邻节点高度标志
          wheelZoom: 0.8, // 滚轮缩放开关，不使用时不设置[0,1]
        };
      },
    },
    // 图谱数据
    graphData: {
      type: Object,
      default: () => {},
    },
    // 工具栏配置
    toolBar: {
      type: Object,
      default: () => {
        return {
          list: [
            'zoomIn', // 缩小
            'zoomOut', // 放大
            'saveImage', // 保存图片
            'saveJson', // 保存json文件
            'overView', // 缩略图显示
            'clockwise', // 顺时针
            'counterclockwise', // 逆时针
            'normalModel', // 正常模式
            'dragModel', // 拖拽模式
            'selectModel', // 框选模式
            'screenFull', // 全屏
          ],
          position: 'left', // 可选  left right top bottom
        };
      },
    },
    // 选中节点id list
    currentNode: {
      type: Array,
      required: false,
      default: [],
    },
  },
  data() {
    return {
      // 画布实例
      visgraph: null,
      // 节点信息弹框
      nodeInfoDrawer: false,
      // 节点信息表单
      nodeInfoForm: {
        label: '',
        id: '',
      },
      // 节点信息弹框tab选项名称
      nodeInfoActiveName: 'first',
      // 关联关系
      nodeInfoRelationActive: ['1', '2'],
      // 目标节点列表
      nodeInfoTargetList: [],
      // 来源节点列表
      nodeInfoSourceList: [],
      // 节点属性列表
      nodeInfoAttributeList: [],
      // 节点配置弹框
      nodeConfigDrawer: false,
      // 节点配置表单
      nodeConfigForm: {
        label: '',
        shape: '',
        fillColor: '',
        hexColor: '',
        size: '',
        borderWidth: '',
        borderDash: '',
        borderColor: '',
        borderHexColor: '',
        textPosition: '',
        font: '',
        fontColor: '',
        fontHexColor: '',
        fontBgColor: '',
        fontBgHexColor: '',
        textOffset: '',
      },
      // 节点类型列表
      nodeTypeList: [
        { value: 'circle', label: '圆形' },
        { value: 'rect', label: '矩形' },
        { value: 'ellipse', label: '椭圆形' },
        { value: 'star', label: '五角形' },
        { value: 'triangle', label: '三角形' },
        { value: 'polygon', label: '六边形' },
      ],
      // 字体位置列表
      textPositionList: [
        { value: 'Middle_Center', label: '居中' },
        { value: 'Bottom_Center', label: '底部' },
        { value: 'top_Center', label: '顶部' },
        { value: 'Middle_Left', label: '左方' },
        { value: 'Middle_right', label: '右方' },
      ],
    };
  },
  mounted() {
    this.createCanvas();
    window.onresize = () => {
      if (this.visgraph) {
        this.visgraph.setZoom();
      }
    };
  },
  methods: {
    /**
     * 创建画布实例
     * @date 2021-08-02
     */
    createCanvas() {
      this.configEvent();
      this.visgraph = new VisGraph(document.getElementById(this.canvasId), this.canvasConfig);
      this.visgraph.clearAll();
      this.visgraph.drawData(this.graphData);
    },
    /**
     * 设置事件
     * @date 2021-08-02
     */
    configEvent() {
      const _this = this;
      // 节点点击
      this.canvasConfig.node.onClick = function (event, node) {
        _this.nodeClick(event, node);
      };
      // 节点双击
      this.canvasConfig.node.ondblClick = function (event, node) {
        _this.nodedbClick(event, node);
      };
      // canvas点击
      this.canvasConfig.noElementClick = function (event, _graphvis) {
        // 点击画布其他位置,弹框隐藏
        _this.nodeMenuDialogClose();
      };
    },
    /**
     * 节点点击
     * @date 2021-08-02
     * @param {any} event
     * @param {any} node
     */
    nodeClick(event, node) {
      // console.log(node)
    },
    /**
     * 节点双击
     * @date 2021-08-02
     * @param {any} event
     * @param {any} node
     */
    nodedbClick(event, node) {
      this.visgraph.restoreHightLight(); // 取消高亮
      const allNodes = this.visgraph.getVisibleData();
      this.currentNode.push(node.id);
      allNodes.nodes.forEach(item => {
        if (this.currentNode.indexOf(item.id) === -1) {
          this.visgraph.deleteNode(item);
        }
      });
      const findNodeNum = Math.round(Math.random() * 50);
      const increamData = this.buildIncreamData(node, findNodeNum);
      this.visgraph.activeAddNodeLinks(increamData.nodes, increamData.links);
      this.visgraph.translateToCenter();
    },
    /**
     * 随机产生节点
     * @date 2021-07-23
     * @param {当前选中节点} centerNode
     * @param {节点数量} nodeNum
     * @returns {Object}
     */
    buildIncreamData(centerNode, nodeNum) {
      const gxData = {
        nodes: [],
        links: [],
      };
      const count = nodeNum;
      const nodeIdPrefix = 'node_' + Math.round(Math.random() * 1000) + '_';
      for (let i = 0; i < count; i++) {
        gxData.nodes.push({
          id: nodeIdPrefix + i,
          label: '子节点+' + i,
          size: 60,
          // color: this.getRandomColor()
        });
        gxData.links.push({
          source: centerNode.id,
          target: nodeIdPrefix + i,
          label: '关系' + i,
        });
      }
      return gxData;
    },
    /**
     * 画布右键事件
     * @date 2021-07-26
     * @param {Object} event
     */
    globalClickedDispatch(event) {
      if (event.button === 2) {
        if (this.visgraph.currentNode) {
          this.nodeMenuDialogOpen(event, this.visgraph.currentNode);
        }
      }
    },
    /**
     * 右键节点菜单显示
     * @date 2021-07-26
     * @param {Object} event
     * @param {Object} node
     */
    nodeMenuDialogOpen(event, node) {
      let nodeMenuDialog = document.getElementById('nodeMenuDialog');
      nodeMenuDialog.style.left = event.clientX + 'px';
      nodeMenuDialog.style.top = event.clientY - 76 + 'px';
      nodeMenuDialog.style.display = 'block';
      this.checkedNodeInfo = node;
      event.stopPropagation();
    },
    /**
     * 关闭节点菜单
     * @date 2021-07-26
     */
    nodeMenuDialogClose() {
      let nodeMenuDialog = document.getElementById('nodeMenuDialog');
      nodeMenuDialog.style.display = 'none';
    },
    /**
     * 点击节点信息
     * @date 2021-07-26
     */
    clickNodeInfo() {
      this.nodeInfoDrawer = true;
      // 赋值表单
      this.nodeInfoForm = this.checkedNodeInfo;
      // 关联节点
      // 出节点
      const k = this.checkedNodeInfo;
      const g = (k.outLinks || []).map(link => {
        return {
          id: link.target.id,
          label: link.target.label,
          type: link.target.type,
          color: 'rgb(' + link.target.fillColor + ')',
          relationType: link.type || link.label || '--',
        };
      });
      // 入节点
      const h = (k.inLinks || []).map(link => {
        return {
          id: link.source.id,
          label: link.source.label,
          type: link.source.type,
          color: 'rgb(' + link.source.fillColor + ')',
          relationType: link.type || link.label || '--',
        };
      });
      this.nodeInfoTargetList = h;
      this.nodeInfoSourceList = g;
      // 属性赋值
      const list = [];
      const nameList = [
        'id',
        'label',
        'type',
        'cluster',
        'fillColor',
        'shape',
        'size',
        'font',
        'fontColor',
        'x',
        'y',
      ];
      nameList.forEach(item => {
        const obj = {
          name: item,
          value: this.checkedNodeInfo[item],
        };
        list.push(obj);
      });
      this.nodeInfoAttributeList = list;
      this.nodeMenuDialogClose();
    },
    /**
     * 选中关联操作
     * @date 2021-07-26
     */
    selectRelation() {
      this.visgraph.rightMenuOprate('selRelate');
    },
    /**
     * 删除指定节点
     * @date 2021-07-26
     * @returns {any}
     */
    deleteNode() {
      this.visgraph.deleteNode(this.visgraph.currentNode);
      this.nodeMenuDialogClose();
    },
    /**
     * 收起指定节点
     * @date 2021-07-26
     * @returns {any}
     */
    contractNode() {
      if (this.visgraph.currentNode.outLinks.length > 0) {
        this.visgraph.contract(this.visgraph.currentNode);
        this.nodeMenuDialogClose();
      } else {
        this.$message.warning('当前节点无子节点,无法收起');
      }
    },
    /**
     * 展开指定节点
     * @date 2021-07-26
     * @returns {any}
     */
    expandedNode() {
      if (this.visgraph.currentNode.outLinks.length > 0) {
        this.visgraph.expanded(this.visgraph.currentNode);
        this.nodeMenuDialogClose();
      } else {
        this.$message.warning('当前节点无子节点,无法展开');
      }
    },
    /**
     * 以指定节点为中心移动
     * @date 2021-07-26
     * @param {String} id
     */
    moveCenterThisNode(id) {
      const node = this.visgraph.findNodeById(id);
      this.visgraph.moveNodeToCenter(node);
    },
    /**
     * 节点配置
     * @date 2021-07-30
     * @returns {any}
     */
    settingNode() {
      this.nodeMenuDialogClose();
      const {
        id,
        label,
        shape,
        fillColor,
        size,
        borderWidth,
        lineDash,
        borderColor,
        textPosition,
        font,
        fontColor,
        labelBackGround,
        textOffsetX,
      } = this.visgraph.currentNode;
      this.nodeConfigForm.id = id;
      this.nodeConfigForm.label = label;
      this.nodeConfigForm.shape = shape;
      this.nodeConfigForm.fillColor = 'rgb(' + fillColor + ')';
      this.nodeConfigForm.hexColor = this.rgbToHex('rgb(' + fillColor + ')');
      this.nodeConfigForm.size = size;
      this.nodeConfigForm.borderWidth = borderWidth;
      this.nodeConfigForm.borderDash = lineDash.length === 2;
      this.nodeConfigForm.borderColor = 'rgb(' + borderColor + ')';
      this.nodeConfigForm.borderHexColor = this.rgbToHex('rgb(' + borderColor + ')');
      this.nodeConfigForm.textPosition = textPosition;
      this.nodeConfigForm.font = font;
      this.nodeConfigForm.fontColor = 'rgb(' + fontColor + ')';
      this.nodeConfigForm.fontHexColor = this.rgbToHex('rgb(' + fontColor + ')');
      this.nodeConfigForm.fontBgColor = labelBackGround ? 'rgb(' + labelBackGround + ')' : '';
      this.nodeConfigForm.fontBgHexColor = labelBackGround
        ? this.rgbToHex('rgb(' + labelBackGround + ')')
        : '';
      this.nodeConfigForm.textOffset = textOffsetX;
      this.nodeConfigDrawer = true;
    },
    /**
     * 保存节点配置
     * @date 2021-07-30
     * @returns {any}
     */
    saveSetting() {
      const {
        id,
        label,
        shape,
        fillColor,
        hexColor,
        size,
        borderWidth,
        borderDash,
        borderColor,
        borderHexColor,
        textPosition,
        font,
        fontColor,
        fontHexColor,
        fontBgColor,
        fontBgHexColor,
        textOffset,
      } = this.nodeConfigForm;
      const b = this.visgraph.findNodeByAttr('id', id);
      if (b) {
        b.label = label;
        b.size = size;
        b.shape = shape;
        b.fillColor = this.getColorRgb(fillColor);
        b.textPosition = textPosition;
        b.fontColor = this.getColorRgb(fontColor);
        b.labelBackGround = this.getColorRgb(fontBgColor);
        b.font = font;
        b.textOffsetY = Number(textOffset) || 2;
        b.borderWidth = Number(borderWidth) || 2;
        b.borderColor = this.getColorRgb(borderColor);
        b.lineDash = borderDash ? [8, 5] : [0];
        this.visgraph.refresh();
        this.$message({
          message: '修改配置成功!',
          type: 'success',
          duration: 2000,
        });
        this.nodeConfigDrawer = false;
      } else {
        this.$message({
          message: '无法找到选中节点!',
          type: 'error',
          duration: 2000,
        });
      }
    },
    /**
     * 随机获取颜色
     * @date 2021-07-20
     * @returns {String} 样式
     */
    getRandomColor() {
      const r = Math.floor(Math.random() * 256);
      const g = Math.floor(Math.random() * 256);
      const b = Math.floor(Math.random() * 256);
      return 'rgb(' + r + ',' + g + ',' + b + ')';
    },
    /**
     * 颜色选择框变化赋值
     * @date 2021-07-26
     * @param {String} val
     * @param {String} kay
     */
    colorChange(val, key) {
      this.nodeConfigForm[key] = this.hexToRgba(val);
    },
    /**
     * 16进制色值转rgb
     * @date 2021-07-26
     * @param {String} hex
     * @returns {String}
     */
    hexToRgba(hex) {
      return (
        'rgb(' +
        parseInt('0x' + hex.slice(1, 3)) +
        ',' +
        parseInt('0x' + hex.slice(3, 5)) +
        ',' +
        parseInt('0x' + hex.slice(5, 7)) +
        ')'
      );
    },
    /**
     * rgb色值转16进制
     * @date 2021-07-26
     * @param {String} color
     * @returns {String}
     */
    rgbToHex(color) {
      const rgb = color.split(',');
      const r = parseInt(rgb[0].split('(')[1]);
      const g = parseInt(rgb[1]);
      const b = parseInt(rgb[2].split(')')[0]);
      const hex = '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
      return hex;
    },
    /**
     * 去掉rgb
     * @date 2021-07-30
     * @param {String} a
     * @returns {String}
     */
    getColorRgb(a) {
      if (a && a.length > 0) {
        a = a.replace('rgb(', '').replace(')', '');
      } else {
        a = null;
      }
      return a;
    },
    /**
     * 保存图片
     * @date 2021-07-23
     */
    saveImage() {
      this.visgraph.saveImage();
    },
    /**
     * 导出json
     * @date 2021-07-30
     */
    exportJson() {
      this.visgraph.exportJsonFile();
    },
    /**
     * 打开缩略图
     * @date 2021-07-23
     */
    showOverView() {
      this.showOverViewFlag = !this.showOverViewFlag;
      this.visgraph.showOverView(this.showOverViewFlag);
    },
    /**
     * 缩小操作
     * @date 2021-07-23
     */
    setZoomIn() {
      this.visgraph.setZoom('zoomIn');
    },
    /**
     * 放大操作
     * @date 2021-07-23
     */
    setZoomOut() {
      this.visgraph.setZoom('zoomOut');
    },
    /**
     * 顺时针旋转
     * @date 2021-07-23
     */
    clockwiseRotate() {
      this.visgraph.rotateGraph(-10);
    },
    /**
     * 逆时针旋转
     * @date 2021-07-23
     */
    counterclockwiseRotate() {
      this.visgraph.rotateGraph(10);
    },
    /**
     * 设置鼠标模式
     * @date 2021-07-23
     * @param {String} type  drag：拖动模式  select：框选模式   normal：正常模式
     */
    setMouseModel(type) {
      this.visgraph.setMouseModel(type);
    },
    /**
     * 全屏显示
     * @date 2021-07-23
     */
    fullScreen() {
      screenfull.request(this.$refs.canvasRef);
    },
  },
};
</script>

<style lang="less" scoped>
.container {
  position: relative;
  width: 100%;
  height: 100%;

  /* 菜单栏 */
  .toolbar {
    position: absolute;
    z-index: 1000;
    width: 45px;
    height: 100%;
    background-color: #fafafa;
    border-right: 1px solid #e5e2e2;
    ul {
      li {
        height: 35px;
        padding: 0;
        color: #066fba;
        line-height: 35px;
        text-align: center;
        cursor: pointer;
        i {
          font-size: 18px;
        }
        &:hover {
          color: #fff;
          background-color: #6ea36d;
        }
      }
    }
  }
  .left {
    left: 0;
  }
  .right {
    right: 0;
  }
  .top {
    top: 0;
    width: 100%;
    height: 35px;
    ul {
      display: flex;
      li {
        width: 35px;
      }
    }
  }
  .bottom {
    bottom: 0;
    width: 100%;
    height: 35px;
    ul {
      display: flex;
      li {
        width: 35px;
      }
    }
  }

  /* 右键弹框样式 */
  .nodeMenuDialog {
    position: absolute;
    z-index: 100;
    display: none;
    min-width: 100px;
    margin: 0;
    padding: 2px 3px;
    overflow: hidden;
    color: #333;
    background: #f9f9f9;
    border: 1px solid #e3e6eb;
    border-radius: 5px;
    box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.2);
    transform: translate(0, 15px) scale(0.95);
    cursor: pointer;
    transition: transform 0.1s ease-out, opacity 0.1s ease-out;
    li {
      position: relative;
      display: block;
      margin: 0;
      padding: 0 10px;
      line-height: 30px;
      white-space: nowrap;
      text-align: center;
      border-radius: 5px;
      &:hover {
        background-color: #c3e5fd;
      }
    }
  }
  /* 节点信息弹框 */
  .nodeInfo {
    .nodeInfoForm {
      margin: 20px 0;
      padding: 20px 20px 0 20px;
      border: solid 1px #dcdfe6;
      border-right: none;
      border-left: none;
    }
    .nodeInfoRelation {
      padding: 0 20px;
      .nodeInfo-table {
        width: 100%;
        overflow-y: scroll;
        th {
          width: 50%;
          padding: 9px 0 9px 9px;
          text-align: left;
          border: 1px solid #ebeef5;
          &:first-child {
            border-right: none;
          }
        }
        td {
          width: 50%;
          padding: 9px 0 9px 9px;
          border: 1px solid #ebeef5;
          border-top: none;
          &:first-child {
            border-right: none;
          }
        }
      }

      p {
        padding: 20px 0;
        text-align: center;
      }
    }

    .nodeInfoAttribute {
      padding: 0 20px;
    }
  }

  /* 节点配置弹框 */
  .nodeConfig {
    margin: 20px 0;
    padding: 20px 20px 0 20px;
    border: solid 1px #dcdfe6;
    border-right: none;
    border-left: none;
    .form-color {
      display: flex;
      justify-content: space-between;
      .form-input {
        width: calc(100% - 50px);
      }
    }
    .save-setting {
      width: 100%;
      margin-bottom: 20px;
      .el-button {
        width: 100%;
      }
    }
  }
}
</style>
